package com.isyscore.os.metadata.service.impl;

import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.isyscore.os.core.exception.DataFactoryException;
import com.isyscore.os.metadata.enums.FormulaSymbol;
import lombok.extern.slf4j.Slf4j;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.SimpleEvaluationContext;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.*;

import static com.isyscore.os.core.exception.ErrorCode.FORMULA_FORMAT_ERROR;
import static com.isyscore.os.core.exception.ErrorCode.FORMULA_RUN_FAILED;

@Service
@Slf4j
public class FormulaService {

    ExpressionParser parser = new SpelExpressionParser();

    public static Set<String> formulaSymbols;

    static {
        formulaSymbols = Sets.newHashSet();
        Arrays.stream(FormulaSymbol.values()).forEach(symbol -> {
            formulaSymbols.add(symbol.getSymbol());
        });
    }

    /**
     * 测试计算公式
     */
    public void testFormula(String formula, List<String> measures) {
        for (String measure : measures) {
            if (!StrUtil.containsIgnoreCase(formula, measure)) {
                throw new DataFactoryException(FORMULA_FORMAT_ERROR, "公式未包含必要的度量值列：" + formula);
            }
        }
        Expression expression = compileFormula(formula, measures);
        Map<String, Object> formulaParams = Maps.newHashMap();
        for (String measure : measures) {
            BigDecimal testValue = RandomUtil.randomBigDecimal(new BigDecimal(1), new BigDecimal(1000));
            formulaParams.put(measure, testValue);
        }
        doCalulateFormula(expression, formulaParams);
    }

    public Expression compileFormula(String formula, List<String> paramNames) {
        for (String paramName : paramNames) {
            //正则表达替换:"\b \b"限定单个单词而非相同字符
            formula = formula.replaceAll("\\b"+paramName+"\\b", "#"+paramName);
        }
        try {
            return parser.parseExpression(formula.trim());
        } catch (Exception e) {
            log.error("解析计算公式发生异常", e);
            throw new DataFactoryException(FORMULA_FORMAT_ERROR, "编译错误，请检查公式的语法规则是否正确");
        }
    }

    public String doCalulateFormula(Expression formulaExpr, Map<String, Object> formulaParams) {
        String nan = String.valueOf(Double.NaN);
        if (formulaParams == null) {
            return nan;
        }
        Map<String, Double> formulaNumbers = new HashMap<>(formulaParams.size());
        boolean hasInvalidMeasureVal = false;
        for (String paramKey : formulaParams.keySet()) {
            Object paramVal = formulaParams.get(paramKey);
            if (paramVal == null || !NumberUtil.isNumber(paramVal.toString())) {
                hasInvalidMeasureVal = true;
                break;
            }
            //将公式的所有参数转换为double类型，否则可能导致除法运算结果强行取整的问题
            formulaNumbers.put(paramKey, Double.parseDouble(paramVal.toString()));
        }
        if (hasInvalidMeasureVal) {
            return nan;
        }
        try {
            EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
            Set<String> paramNames = formulaNumbers.keySet();
            for (String paramName : paramNames) {
                context.setVariable(paramName, formulaNumbers.get(paramName));
            }
            String formulaResult = Objects.requireNonNull(formulaExpr.getValue(context)).toString();
            if (formulaResult == "Infinity") {
                return nan;
            }
            return NumberUtil.roundStr(formulaResult, 2);
        } catch (ArithmeticException ae) {
            return nan;
        } catch (Exception e) {
            log.error("运行计算公式发生异常", e);
            throw new DataFactoryException(FORMULA_RUN_FAILED);
        }
    }

}
